package user.servlet;

import java.net.*;
import java.sql.SQLException;
import java.util.Collection;

import javax.annotation.Resource;
import javax.ws.rs.*;
import javax.ws.rs.core.*;
import javax.ws.rs.core.Response.Status;

import msg.*;

import org.json.*;

import user.*;

import common.exceptions.*;
import common.extensions.*;
import common.servlet.*;

@Resource
@ApplicationPath("/")
@Path("/users/")
@SuppressWarnings({"javadoc", "static-method"})
public class UsersResource extends AbstractResource {
	public static final String	COOKIE_NAME	= "kuki";

	@GET
	@Produces(MediaType.APPLICATION_JSON)
	public Response list() {
		try (ManagersBundle mb = new ManagersBundle()) {
			Collection<User> users = mb.lm.getAll();
			users = Extender.orderBy(users, new Function<User, String>() {
				@Override
				public String calculate(User element) {
					return element.getName();
				}
			});
			JSONArray array = new JSONArray();
			for (User user : users) {
				array.put(userToJSON(user));
			}

			mb.commit();
			return Response.ok(array).build();
		} catch (RuntimeException | SQLException e) {
			return handleException(e);
		}
	}

	@GET
	@Path("{username}/posts")
	@Produces(MediaType.APPLICATION_JSON)
	public Response listPosts(@PathParam("username") String username) {
		try (ManagersBundle mb = new ManagersBundle()) {
			User user = mb.lm.get(username);
			Collection<Message> posts = mb.mm.getAllUserPosts(user);
			posts = orderMessages(posts);
			mb.commit();
			return msgsToJSONArray(posts);
		} catch (InvalidKeyException e) {
			return Response.status(Status.NOT_FOUND).entity("no such user exists").build();
		} catch (RuntimeException | SQLException e) {
			return handleException(e);
		}
	}

	@GET
	@Path("{username}/events")
	@Produces(MediaType.APPLICATION_JSON)
	public Response listEvents(@PathParam("username") String username, @QueryParam("since") Long since,
			@QueryParam("past") boolean past) {
		User user;
		try (ManagersBundle mb = new ManagersBundle()) {
			user = mb.lm.get(username);
			Collection<EventMessage> events = mb.mm.getallUserEvents(user, since, past);
			mb.commit();
			Collection<Message> sorted = orderMessages(events);
			return msgsToJSONArray(sorted);
		} catch (InvalidKeyException e) {
			return Response.serverError().status(Status.NOT_FOUND).entity("no such user exists").build();
		} catch (RuntimeException | SQLException e) {
			return handleException(e);
		}
	}

	@GET
	@Path("{username}/messages")
	@Produces(MediaType.APPLICATION_JSON)
	public Response listMessages(@PathParam("username") String username, @QueryParam("since") Long since) {
		User user;
		try (ManagersBundle mb = new ManagersBundle()) {
			user = mb.lm.get(username);
			Collection<Message> messages = mb.mm.getallUserMessagesOnly(user, since);
			mb.commit();
			messages = orderMessages(messages);
			return msgsToJSONArray(messages);
		} catch (InvalidKeyException e) {
			return Response.serverError().status(Status.NOT_FOUND).entity("no such user exists").build();
		} catch (RuntimeException | SQLException e) {
			return handleException(e);
		}
	}

	@GET
	@Path("{username}/registrations")
	@Produces(MediaType.APPLICATION_JSON)
	public Response listRegistrations(@PathParam("username") String username, @QueryParam("past") Boolean past) {
		User user;
		try (ManagersBundle mb = new ManagersBundle()) {
			user = mb.lm.get(username);
			Collection<EventMessage> regs = mb.rm.getAllRegistrationsByUser(user, past);
			mb.commit();
			Collection<Message> sorted = orderMessages(regs);
			return msgsToJSONArray(sorted);
		} catch (InvalidKeyException e) {
			return Response.serverError().status(Status.NOT_FOUND).entity("no such user exists").build();
		} catch (RuntimeException | SQLException e) {
			return handleException(e);
		}
	}

	@GET
	@Path("{username}")
	@Produces(MediaType.APPLICATION_JSON)
	public Response retrieve(@PathParam("username") String username) {
		try (ManagersBundle mb = new ManagersBundle()) {
			User user = mb.lm.get(username);
			JSONObject object = userToJSON(user);
			mb.commit();
			return Response.ok().entity(object.toString()).build();
		} catch (InvalidKeyException e) {
			return Response.serverError().status(Status.NOT_FOUND).entity("no such user exists").build();
		} catch (RuntimeException | SQLException e) {
			return handleException(e);
		}
	}

	@POST
	@Path("{username}/unregister")
	public Response unregister(@PathParam("username") String username, JSONObject object) {
		try (ManagersBundle mb = new ManagersBundle()) {
			String password = object.getString("password");
			mb.lm.checkPassword(username, password);
			try {
				User user = mb.lm.get(username);
				mb.rm.removeAllFromUser(user);
				mb.mm.removeAllUserMessagesAndEvents(user);
				boolean result = mb.lm.deleteUser(username, password);
				assert result;
			} catch (InvalidKeyException e) {
				// something bad must have happened w.r.t. concurrency
				throw new SQLException(e);
			}
			// password was checked
			mb.commit();
			return Response.ok().build();
		} catch (WrongPasswordException e) {
			return Response.serverError().status(Status.FORBIDDEN).entity("wrong pasword").build();
		} catch (JSONException e) {
			return handleJsonException(e);
		} catch (RuntimeException | SQLException e) {
			return handleException(e);
		}
	}

	@POST
	@Path("{username}")
	public Response register(@PathParam("username") String username, JSONObject object) {
		try (ManagersBundle mb = new ManagersBundle()) {
			String name = object.getString("name");
			String password = object.getString("password");
			mb.lm.registerUser(username, name, password);
			mb.commit();
			return Response.created(new URI("")).build();
		} catch (ExistingEntityException e) {
			return Response.serverError().status(Status.CONFLICT).entity("already exists").build();
		} catch (JSONException e) {
			return handleJsonException(e);
		} catch (RuntimeException | URISyntaxException | SQLException e) {
			// To the idiot that decided that URI exception is not runtime - I will hunt you down, and kill you.
			return handleException(e);
		}
	}

	@POST
	@Path("/login/{username}")
	@Produces(MediaType.APPLICATION_JSON)
	public Response login(@PathParam("username") String username, JSONObject object) {
		try (ManagersBundle mb = new ManagersBundle()) {
			String password = object.getString("password");
			Session session = mb.lm.loginUser(username, password);
			JSONObject result = new JSONObject();
			result.put("id", session.getId());
			result.put("user", userToJSON(session.getUser()));
			NewCookie cookie = new NewCookie(COOKIE_NAME, session.getId().toString());
			result.put("cookie", cookie.getValue());
			result.put("cookie-name", cookie.getName());
			mb.commit();
			return Response.ok(result).cookie(cookie).build();
		} catch (JSONException e) {
			return handleJsonException(e);
		} catch (WrongPasswordException e) {
			return Response.status(Status.FORBIDDEN).entity(e.getMessage()).build();
		} catch (RuntimeException | SQLException e) {
			return handleException(e);
		}
	}

	@DELETE
	@Path("/login/{username}")
	public Response logout(@PathParam("username") String username,
			@CookieParam(value = COOKIE_NAME) Long sessionId) {
		try (ManagersBundle mb = new ManagersBundle()) {
			if (sessionId == null) {
				return missingCookie();
			}
			Session session = mb.lm.getSession(sessionId);
			// verify that the session id and username match
			if (session.getUser().getId().equals(username) == false) {
				return Response.status(Status.FORBIDDEN).entity("session id belongs to another user").build();
			}
			mb.lm.logoutUser(sessionId);
			mb.commit();
			return Response.ok().build();
		} catch (NumberFormatException e) {
			return handleUserException(e);
		} catch (InvalidKeyException e) {
			return Response.noContent().build();
		} catch (RuntimeException | SQLException e) {
			return handleException(e);
		}
	}

	@GET
	@Produces(MediaType.APPLICATION_JSON)
	@Path("/me")
	public Response me(@CookieParam(value = COOKIE_NAME) Long sessionId) {
		try (ManagersBundle mb = new ManagersBundle()) {
			if (sessionId == null) {
				return missingCookie();
			}

			Session session = mb.lm.getSession(sessionId);
			JSONObject object = userToJSON(session.getUser());
			mb.commit();
			return Response.ok().entity(object.toString()).build();
		} catch (NumberFormatException e) {
			return handleUserException(e);
		} catch (InvalidKeyException e) {
			return missingCookie();
		} catch (RuntimeException | SQLException e) {
			return handleException(e);
		}
	}

	private Response msgsToJSONArray(Collection<? extends Message> msgs) {
		JSONArray array = new JSONArray();
		for (Message msg : msgs) {
			array.put(toJSON(msg));
		}

		return Response.ok(array).build();
	}
}
